
/*
 * (a)  (C) 1990 by Adobe Systems Incorporated. All rights reserved.
 *
 * (b)  If this Sample Code is distributed as part of the Display PostScript
 *	System Software Development Kit from Adobe Systems Incorporated,
 *	then this copy is designated as Development Software and its use is
 *	subject to the terms of the License Agreement attached to such Kit.
 *
 * (c)  If this Sample Code is distributed independently, then the following
 *	terms apply:
 *
 * (d)  This file may be freely copied and redistributed as long as:
 *	1) Parts (a), (d), (e) and (f) continue to be included in the file,
 *	2) If the file has been modified in any way, a notice of such
 *      modification is conspicuously indicated.
 *
 * (e)  PostScript, Display PostScript, and Adobe are registered trademarks of
 *	Adobe Systems Incorporated.
 * 
 * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO
 *	CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED
 *	AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED.
 *	ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY
 *	OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO
 *	WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY)
 *	WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY
 *	DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, 
 *	FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT
 *	OF THIRD PARTY RIGHTS.
 */

/*
 *	DocView.m
 *
 *	This class handles the scaling of the drawing view. It repositions
 *	The drawing view within itself if the drawing view is smaller than the
 *	size of the clip view. The size of the doc view is:
 *	MAX(clip view frame, drawing view frame).
 *
 *	Version:	2.0
 *	Author:	Ken Fromm
 *	History:
 *			03-07-91		Added this comment.
 */

#import "DocView.h"

#import <appkit/NXCursor.h>
#import <appkit/NXImage.h>
#import <appkit/Matrix.h>
#import <appkit/ScrollView.h>
#import <dpsclient/wraps.h>
#import <appkit/nextstd.h>

@implementation DocView

/*
*	Since there is only one subview its easier to keep track of it as an
*	instance variable rather than in the subview list.  Returns any the
*	previous drawing view.
*/
- setDrawView:subView
{
	id		oldView;

	oldView = drawviewId;
	[oldView  removeFromSuperview];

	[self  addSubview:subView];
	drawviewId = subView;
	
	return oldView;
}

- drawView;
{
	return drawviewId;
}

- setZoomControl:anObject
{
	zoomControl = anObject;
	
	return self;
}

- zoomControl
{
	return zoomControl;
}

- setHitSetting:(float) value
{	
	hitsetting = value;

	return self;	
}

/*
*	Scale the hit setting. Using an unscaled hit setting would be like
*	using a boxing glove on a 400% scale.
*/
- (float) hitSetting
{	
	return hitsetting * (1.0/scale);
}

- setScale:(float)value
{
	scale = value;
	
	return self;
}

- (float) scale
{
	return  scale;
}

- (BOOL) isZooming
{
	return zooming;
}

/*
*	Check whether the frame of the drawingview is in the rectangle passed in.
*	If drop is true, then include the linewidth and the drop shadow as part of the
*	drawingview frame.
*/
- getDrawViewInRect:(const NXRect *) aRect  withDrop:(BOOL) drop
{
	id		aView = NULL;

	NXRect	viewRect, dropRect;
	
	[drawviewId  getFrame:&viewRect];
	if (drop)
	{	
		dropRect = viewRect;
		NXOffsetRect(&dropRect, rint(OFFSET * scale), rint(-OFFSET * scale));
		NXOffsetRect(&viewRect, rint(LINEWIDTH/2 * scale), rint(LINEWIDTH/2 * scale));
		NXUnionRect(&dropRect, &viewRect);
	}
	if (NXIntersectsRect(aRect, &viewRect))
		aView = drawviewId;

	return  aView;
}

/*
*	This method is overridden from the View class. When the
*	user is zooming the cursor rect for the zoom cursor is set
*	the the frame of the drawing view.
*/
- resetCursorRects
{
	id			imageId, visView;

	NXPoint		hotspot;

	NXRect		cursorRect, visRect;

	if (zooming)
	{
		if (!zoomCursor)
		{
			hotspot.x = hotspot.y = 8.0;
			imageId = [NXImage  newFromSection:"zoom.tiff"];
			zoomCursor = [NXCursor  newFromImage:imageId];
			[zoomCursor setHotSpot:&hotspot];
		}

		[self getVisibleRect:&visRect];
		visView = [self getDrawViewInRect:&visRect  withDrop:NO];
		if (visView)
		{
			[visView getFrame:&cursorRect];
			NXIntersectionRect(&visRect, &cursorRect);
			[self addCursorRect:&cursorRect  cursor:zoomCursor];
		}		
		else
			zooming = NO;
	}

	return self;
}

/*
*	Messaged from the zoom matrix. Starts the zoom loop. At the next
*	mouse down in the drawing view, the scaleDrawView:toPoint: method
*	will be called.
*/
- zoom:sender
{
	zooming = YES;
	[self  setZoomControl:sender];
	[self  resetCursorRects];

	[window makeKeyAndOrderFront:self];

	return self;
}

/*
*	Sizes the drawing view from the old scale to the newscale.
*	The frame of the view is used instead of the bounds because
*	the bound will always be the same whereas the frame will
*	provide the size in default coordinates.
*/
- sizeView:viewId withScale:(float) newscale
{
	float			scalefactor, sizewidth, sizeheight;

	NXRect		viewFrame;
	
	[viewId  getFrame:&viewFrame];
	scalefactor = newscale/scale;
	sizewidth = viewFrame.size.width*scalefactor;
	sizeheight = viewFrame.size.height*scalefactor;
	[viewId sizeTo:rint(sizewidth) :rint(sizeheight)];

	return self;
}

/* 
*	Scales the drawing view from the old scale to the new scale.
*	The scale method is relative so we use the ratio
*	of the old scale to the new scale to obtain the scaling factor.
*/
- scaleView:viewId  withScale:(float) newscale
{
	float 	scalefactor;

	scalefactor = newscale/scale;
	[viewId scale:scalefactor  :scalefactor];

	return self;
}

/*
*	Place the view passed in in the center of the doc view
*	if it is smaller than the size of the ClipView. Two passes are made
*	through the loop because adding or removing a horizontal or
*	vertical scrollbar will affect the size of the remaining dimension .
*/
- placeView:viewId
{
	BOOL		vert = NO, horiz = NO, done = NO;

	int			passes = 0, border_type;

	float			margin;
	
	NXSize		contSize, newSize, insetSize, delta;

	NXRect 		viewFrame, scrollFrame;

	margin = 4 * OFFSET * scale;

	[viewId  getFrame:&viewFrame];
	[[superview superview]  getFrame:&scrollFrame];
	border_type = [[superview superview]  borderType];

	while (!done && ++passes <= 2)
	{
		[ScrollView getContentSize:&contSize forFrameSize:&scrollFrame.size
			horizScroller:horiz  vertScroller:vert borderType:border_type];

		newSize.width = MAX(viewFrame.size.width, contSize.width);
		newSize.height = MAX(viewFrame.size.height, contSize.height);

		delta.width = newSize.width - viewFrame.size.width;
		delta.height = newSize.height - viewFrame.size.height;
		if (delta.width != 0.0 || delta.height != 0.0)
		{
			if (delta.width < margin)
				newSize.width += margin - delta.width;
			if (delta.height < margin)
				newSize.height += margin - delta.height;
		}

		horiz = (newSize.width != contSize.width);
		vert = (newSize.height != contSize.height);
		if ((horiz && vert) || (!horiz && !vert))
			done = YES;
		else
			done = NO;
	}
	
	[self  sizeTo:newSize.width  :newSize.height];

	insetSize.width = floor((newSize.width - viewFrame.size.width)/2);
	insetSize.height =  floor((newSize.height - viewFrame.size.height)/2);
	[viewId  moveTo:insetSize.width  :insetSize.height];
	if (insetSize.width != 0 || insetSize.height != 0)
		[viewId  setClipping:YES];
	else
		[viewId setClipping:NO];

	if (horiz)
		[[superview superview] setHorizScrollerRequired:YES];
	else
		[[superview superview] setHorizScrollerRequired:NO];
	
	if (vert)
		[[superview superview] setVertScrollerRequired:YES];
	else
		[[superview superview] setVertScrollerRequired:NO];

	return self;
}

/*
*	Scales the view to the tag of the selected matrix cell and then
*	scrolls to the point. The point is in window coordinates so
*	it must be converted to the drawing view  coordinates in order
*	to position it in the same relative place in the view.
*/
- scaleDrawView:hitView  toPoint:(NXPoint *) p;
{
	float			newscale;

	NXPoint		viewPt;

	[window invalidateCursorRectsForView:self];
	newscale = [zoomControl  selectedTag] * 0.01;
	if (hitView && newscale != scale)
	{
		viewPt = *p;
		[self convertPoint:&viewPt fromView:nil];
		[self convertPoint:&viewPt  toView:hitView];
		[window disableDisplay];
			/* Size and then scale the drawing view. The frame of the */
			/* drawing view must reflect the changed size. Scaling */
			/* alone will not change the frame. */
			[self  sizeView:hitView  withScale:newscale];
			[self  scaleView:hitView  withScale:newscale];
			[self  setScale:newscale];
	
			[self  placeView:hitView];
			[self  scrollPoint:&viewPt  inView:hitView  to:p];
		[window reenableDisplay];
		[[window contentView]  display];
	}
	zooming = NO;

	return self;
}


/*
 *	Use the point location relative to the window versus the point relative
 *	to the drawing view (before the scale) to determine where to scroll
 *	the drawing view. We want the same location in the drawing view to
 *	appear where the mouse downed occurred.  The window coordinates
 *	are used and then adjusted by the position of the scrolling frame
 *	because the presence or absence of scrollbars throws the use
 *	of the doc views coordinates awry.
 */	
- scrollPoint:(const NXPoint *) aPt  inView:hitView  to:(const NXPoint *) windowPt
{
	NXPoint		viewPt;

	NXSize		contSize;

	NXRect 		viewFrame, visRect, scrollFrame;

	[[superview superview]  getContentSize:&contSize];
	[hitView  getFrame:&viewFrame];

	if (viewFrame.size.width > contSize.width || viewFrame.size.height > contSize.height)
	{
		viewPt = *aPt;
		[self  convertPoint:&viewPt  fromView:hitView];		
		[self getVisibleRect:&visRect];
		[[superview superview] getFrame:&scrollFrame];

		if (viewFrame.size.width > contSize.width)
			viewPt.x  -= windowPt->x - (scrollFrame.origin.x +scrollFrame.size.width -
							visRect.size.width);

		if (viewFrame.size.height > contSize.height)
			viewPt.y  -= windowPt->y - (scrollFrame.origin.y + scrollFrame.size.height -
							visRect.size.height);

		[self  scrollPoint:&viewPt];		
	}

	return self;
}

/*
 * This drawSelf method draws a border around the drawing view as well as a
 * drop shadow.  This is only done if it will be visible however. We tell if it is visible
 * if the rectangle passed in is not contained entirely within drawingView frame.
*/
- drawSelf:(NXRect *)r :(int) count
{
	id			visView;

	NXRect		drawRect, dropRect;

	visView = [self getDrawViewInRect:r  withDrop:YES];
	if (visView)
	{
		[visView  getFrame:&drawRect];
		if (!NXContainsRect(&drawRect, r))
		{
			dropRect = drawRect;
			NXOffsetRect(&dropRect, rint(OFFSET * scale), rint(-OFFSET * scale));

			PSgsave();
			PSsetgray(NX_BLACK);
			PSsetlinewidth(LINEWIDTH * scale);
			PSrectclip(r->origin.x, r->origin.y, r->size.width, r->size.height);
			PSrectfill(dropRect.origin.x, dropRect.origin.y,
				dropRect.size.width, dropRect.size.height);
			PSrectstroke(drawRect.origin.x, drawRect.origin.y,
				drawRect.size.width, drawRect.size.height);
			PSgrestore();
		}
	}

	return self;			
}

@end

